package com.legendframework.core.dao.sql.core;


import com.google.gson.*;
import com.legendframework.core.dao.annotation.Column;
import com.legendframework.core.dao.annotation.TableId;
import com.legendframework.core.dao.annotation.ToJson;
import com.legendframework.core.dao.support.ClassUtils;
import com.legendframework.core.dao.transaction.JdbcTransaction;
import com.legendframework.core.dao.transaction.enums.TransactionStatus;
import com.legendframework.core.dao.transaction.pojo.SqlTransaction;
import com.legendframework.core.dao.utils.DaoStringUtils;

import javax.sql.DataSource;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;
import java.util.Date;

/**
 * @ClassName JdbcTemplate
 * @Author White.
 * @Date 2019/7/14 15:26
 * @Version 1.0
 * 对执行语句的封装类
 */
public class JdbcTemplate implements JdbcOperations {

    private final static int NULL_PARAMS_TYPE = -2147483648;

    /**
     * 数据源
     */
    private final DataSource dataSource;

    private Gson gson;

    private final JdbcTransaction jdbcTransaction;

    /**
     * 获取连接
     *
     * 支持事务
     *
     * @return
     */
    private Connection getConnection() {
        //获取当前事务的连接
        Connection connection = getTransactionConnection();
        if (connection != null) {
            return connection;
        }
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    /**
     * 获取当前事务的连接
     *
     * 如果没有开启事务则返回null
     * @return
     */
    private Connection getTransactionConnection() {
        if (jdbcTransaction == null) {
            return null;
        }
        Deque<SqlTransaction> threadLocalData = jdbcTransaction.getThreadLocalData();
        SqlTransaction sqlTransaction = threadLocalData.peek();
        if (sqlTransaction != null && sqlTransaction.getStatus() == TransactionStatus.BIGIN) {
            return sqlTransaction.getConnection();
        }
        return null;
    }

    /**
     * 设置参数
     *
     * @param statement
     * @param param
     * @return
     */
    private PreparedStatement setValue(PreparedStatement statement, Object[] param) throws SQLException {
        for (int i = 0; i < param.length; i++) {
            Object o = param[i];
            if (o instanceof Integer) {
                statement.setInt(i + 1, (Integer) o);
            } else if (o instanceof Long) {
                statement.setLong(i + 1, (Long) o);
            } else if (o instanceof Byte) {
                statement.setByte(i + 1, (Byte) o);
            } else if (o instanceof Short) {
                statement.setShort(i + 1, (Short) o);
            } else if (o instanceof Float) {
                statement.setFloat(i + 1, (Float) o);
            } else if (o instanceof Double) {
                statement.setDouble(i + 1, (Double) o);
            } else if (o instanceof String) {
                statement.setString(i + 1, (String) o);
            } else if (o instanceof Boolean) {
                statement.setBoolean(i + 1, (Boolean) o);
            } else if (o instanceof Date) {
                statement.setDate(i + 1, new java.sql.Date(((Date) o).getTime()));
            } else if (o instanceof Enum) {
                statement.setString(i + 1, o.toString());
            } else {
                statement.setNull(i + 1, NULL_PARAMS_TYPE);
            }
        }
        return statement;
    }

    /**
     * 将Set结果集的数据封装到实体类中
     *
     * @param resultSet
     * @param cls
     * @param <T>
     * @return
     * @throws SQLException
     */
    private <T> List<T> encapsulationData(ResultSet resultSet, Class<T> cls) throws SQLException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        List<T> list = new ArrayList<>();
        while (resultSet.next()) {
            //实例化指定Class的对象
            T t = cls.newInstance();
            //获取该类的所有属性
            List<Field> fields = ClassUtils.getByClass(cls);
            //遍历实体类中的属性
            for (Field field : fields) {
                //将实体类中的属性名转换成表的字段名
                String lineName = DaoStringUtils.humpToLine(field.getName());
                //获取注解中的字段名
                if (field.isAnnotationPresent(Column.class)) {
                    Column annotation = field.getAnnotation(Column.class);
                    //如果字段不映射则跳过该字段
                    if (!annotation.exist()) {
                        continue;
                    }
                    if (DaoStringUtils.isNotEmpty(annotation.value())) {
                        lineName = DaoStringUtils.humpToLine(annotation.value());
                    }
                }
                //设置主键的字段
                if (field.isAnnotationPresent(TableId.class)) {
                    TableId annotation = field.getAnnotation(TableId.class);
                    if (DaoStringUtils.isNotEmpty(annotation.value())) {
                        lineName = DaoStringUtils.humpToLine(annotation.value());
                    }
                }
                //实体类的某属性对象值
                Object fieldTypeObj = null;
                //判断是否为复杂类型
                if (Objects.nonNull(field.getAnnotation(ToJson.class))) {
                    //从resultSet结果集中获取指定字段名的值
                    String json = resultSet.getObject(lineName, String.class);
                    if (DaoStringUtils.isNotEmpty(json)) {
                        //获取属性值
                        if (List.class.equals(field.getType())) {
                            List resultList = new ArrayList();
                            JsonArray arry = new JsonParser().parse(json).getAsJsonArray();
                            Type genericType = field.getGenericType();
                            Class<?> actualTypeArgument = null;
                            if (genericType != null) {
                                if (genericType instanceof ParameterizedType) {
                                    ParameterizedType pt = (ParameterizedType) genericType;
                                    // 得到泛型里的class类型对象
                                    actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
                                }
                            }
                            if (actualTypeArgument != null) {
                                for (JsonElement jsonElement : arry) {
                                    resultList.add(gson.fromJson(jsonElement, actualTypeArgument));
                                }
                                fieldTypeObj = resultList;
                            }
                        } else if (Set.class.equals(field.getType())) {
                            Set resultList = new HashSet();
                            JsonArray arry = new JsonParser().parse(json).getAsJsonArray();
                            Type genericType = field.getGenericType();
                            Class<?> actualTypeArgument = null;
                            if (genericType != null) {
                                if (genericType instanceof ParameterizedType) {
                                    ParameterizedType pt = (ParameterizedType) genericType;
                                    // 得到泛型里的class类型对象
                                    actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
                                }
                            }
                            if (actualTypeArgument != null) {
                                for (JsonElement jsonElement : arry) {
                                    resultList.add(gson.fromJson(jsonElement, actualTypeArgument));
                                }
                                fieldTypeObj = resultList;
                            }
                        } else if (Map.class.equals(field.getType())) {
                            Map resultMap = new HashMap();
                            JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
                            Type genericType = field.getGenericType();
                            //泛型类型
                            Class<?> actualTypeArgumentOne = null;
                            Class<?> actualTypeArgumentTwe = null;
                            if (genericType != null) {
                                if (genericType instanceof ParameterizedType) {
                                    ParameterizedType pt = (ParameterizedType) genericType;
                                    // 得到泛型里的class类型对象
                                    actualTypeArgumentOne = (Class<?>) pt.getActualTypeArguments()[0];
                                    actualTypeArgumentTwe = (Class<?>) pt.getActualTypeArguments()[1];
                                }
                            }
                            if (actualTypeArgumentOne != null && actualTypeArgumentTwe != null) {
                                for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
                                    String key = entry.getKey();
                                    JsonElement value = entry.getValue();
                                    Object objKey = null;
                                    //转换key
                                    if (Integer.class.equals(actualTypeArgumentOne)) {
                                        objKey = Integer.parseInt(key);
                                    } else if (Long.class.equals(actualTypeArgumentOne)) {
                                        objKey = Long.parseLong(key);
                                    } else if (Byte.class.equals(actualTypeArgumentOne)) {
                                        objKey = Byte.parseByte(key);
                                    } else if (Short.class.equals(actualTypeArgumentOne)) {
                                        objKey = Short.parseShort(key);
                                    } else if (Float.class.equals(actualTypeArgumentOne)) {
                                        objKey = Float.parseFloat(key);
                                    } else if (Double.class.equals(actualTypeArgumentOne)) {
                                        objKey = Double.parseDouble(key);
                                    } else if (String.class.equals(actualTypeArgumentOne)) {
                                        objKey = key;
                                    } else if (Boolean.class.equals(actualTypeArgumentOne)) {
                                        objKey = Boolean.parseBoolean(key);
                                    }
                                    resultMap.put(objKey, gson.fromJson(value, actualTypeArgumentTwe));
                                }
                                fieldTypeObj = resultMap;
                            }
                        } else {
                            fieldTypeObj = gson.fromJson(json, field.getType());
                        }
                    }
                } else if (field.getType().isEnum()) {
                    String enumName = resultSet.getObject(lineName, String.class);
                    if (DaoStringUtils.isNotEmpty(enumName)) {
                        Class<? extends Enum> type = (Class<? extends Enum>) field.getType();
                        fieldTypeObj = Enum.valueOf(type, enumName);
                    }
                } else {
                    fieldTypeObj = resultSet.getObject(lineName, field.getType());
                }
                //获得实体类中属性名对于的set方法
                Method method = cls.getMethod(DaoStringUtils.fieldNameToSetterMethodName(field.getName()), field.getType());
                //执行set方法属性装载成功
                method.invoke(t, fieldTypeObj);
            }
            list.add(t);
        }
        return list;
    }

    /**
     * 关闭资源
     *
     * @param resultSet
     * @param statement
     */
    private void close(ResultSet resultSet, PreparedStatement statement, Connection conn) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                //如果当前没有开启事务则关闭连接
                if (getTransactionConnection() == null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 查询所有
     *
     * @param sql   执行的select语句
     * @param cls   泛型类型
     * @param param 替换字符的参数列表
     * @param <T>
     * @return
     */
    @Override
    public <T> List<T> findAll(String sql, Class<T> cls, Object... param) {
        Connection connection = getConnection();
        ResultSet resultSet = null;
        PreparedStatement statement = null;
        List<T> list = new ArrayList<>();
        if (connection == null) {
            return new ArrayList<>();
        }
        try {
            statement = connection.prepareStatement(sql);
            //设置参数值
            setValue(statement, param);
            resultSet = statement.executeQuery();
            try {
                list = encapsulationData(resultSet, cls);
            } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            close(resultSet, statement, connection);
        }
        return list;
    }

    /**
     * 查询一个
     *
     * @param sql     执行的select语句
     * @param cls     返回值类型
     * @param throwEx 有多个 result 是否抛出异常
     * @param param   替换字符的参数列表
     * @param <T>
     * @return
     */
    public <T> T findOne(String sql, Class<T> cls, boolean throwEx, Object... param) {
        List<T> list = findAll(sql, cls, param);
        if (throwEx) {
            //断言结果只有一个
            assert list != null && list.size() <= 1;
        }
        if (list.size() == 0) {
            return null;
        }
        return list.get(0);
    }

    /**
     * 查询记录数
     *
     * @param sql   执行的select语句
     * @param param 替换字符的参数列表
     * @return
     */
    @Override
    public long findCount(String sql, Object... param) {
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        long count = 0;
        Connection connection = getConnection();
        if (connection == null) {
            return 0;
        }
        try {
            statement = connection.prepareStatement(sql);
            //设置参数值
            setValue(statement, param);
            resultSet = statement.executeQuery();
            while (resultSet.next()) {
                count = resultSet.getInt(1);
            }
            return count;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            close(resultSet, statement, connection);
        }
        return 0;
    }

    /**
     * 修改
     *
     * @param sql   修改的语句
     * @param param 替换字符的参数列表
     * @return
     */
    @Override
    public int update(String sql, Object... param) {
        Connection connection = getConnection();
        PreparedStatement statement = null;
        int count = 0;
        if (connection == null) {
            return 0;
        }
        try {
            statement = connection.prepareStatement(sql);
            setValue(statement, param);
            count = statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            close(null, statement, connection);
        }
        return count;
    }

    /**
     * 删除
     *
     * @param sql   sql语句
     * @param param 替换字符的参数列表
     * @return
     */
    @Override
    public int delete(String sql, Object... param) {
        return update(sql, param);
    }

    /**
     * 新增
     *
     * @param sql   sql语句
     * @param param 替换字符的参数列表
     * @return
     */
    @Override
    public int insert(String sql, Object... param) {
        return update(sql, param);
    }

    /**
     * 校验表是否存在
     *
     * @param tableName 表名
     * @return
     */
    @Override
    public boolean validateTableExist(String tableName) {
        boolean flag = false;
        try {
            Connection connection = getConnection();
            DatabaseMetaData meta = connection.getMetaData();
            String[] type = {"TABLE"};
            ResultSet rs = meta.getTables(null, null, tableName, type);
            flag = rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return flag;
    }

    public JdbcTemplate(DataSource dataSource , JdbcTransaction jdbcTransaction) {
        this.dataSource = dataSource;
        this.jdbcTransaction = jdbcTransaction;
        this.gson = new GsonBuilder().serializeNulls().create();
    }

    public DataSource getDataSource() {
        return dataSource;
    }

}
